/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.ui.dialogs;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Package;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.utils.LookForElement;

/**
 * 
 * @author ymortier <yann.mortier@obeo.fr>
 */
public class UmlGenericTreeSelectionDialog extends Dialog {

	/** selected value. */
	private Element selectedValue;

	/** The selection tree */
	protected Tree tree;

	/** the top package, ie profile/package/model root of the model */
	protected Package model;

	/** The filter. */
	private ChainFilter filter = new ChainFilter();

	/**
	 * Creates a new <code>UmlGenericTreeSelectionDialog</code>.
	 * 
	 * @param parent
	 *            the parent.
	 * @param model
	 *            the model.
	 */
	public UmlGenericTreeSelectionDialog(Shell parent, Package model) {
		super(parent);
		this.model = model;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
	 */
	protected Control createDialogArea(Composite parent) {
		Composite composite = (Composite) super.createDialogArea(parent);
		tree = new Tree(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL);

		TreeItem item = new TreeItem(tree, SWT.NONE);
		item.setText(model.getName());
		item.setData(model);
		item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(model));
		List<Element> candidatesModel = this.findAllCandidates(model);
		for (Element element : candidatesModel) {
			List<Element> path = getAllParentsUntil(model, element);
			TreeItem current = item;
			for (Element pathElement : path) {
				current = findOrCreateTreeItem(current, pathElement);
			}
		}

		// build basic imported packages list
		HashSet<Package> importedPackages = LookForElement
				.buildImportedTopPackagesList(model, new HashSet<Package>());

		// extends this list to all imported packages (import from import,
		// etc...)
		Iterator<Package> packages = LookForElement
				.buildExtendedImportedPackagesList(importedPackages, model)
				.iterator();

		while (packages.hasNext()) {
			Package currentPackage = (Package) packages.next();
			item = new TreeItem(tree, SWT.NONE);
			item.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(
					currentPackage, true));
			item.setData(currentPackage);
			item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER
					.getImage(currentPackage));
			List<Element> candidates = this.findAllCandidates(currentPackage);
			for (Element element : candidates) {
				List<Element> path = getAllParentsUntil(currentPackage, element);
				TreeItem current = item;
				for (Element pathElement : path) {
					current = findOrCreateTreeItem(current, pathElement);
				}
			}
		}
		tree.setLayoutData(new GridData(300, 300));

		tree.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				setSelection(e);
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		return composite;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
	 */
	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText("Selection");
	}

	private List<Element> findAllCandidates(Package package_) {
		TreeIterator iter = package_.eAllContents();
		ArrayList<Element> list = new ArrayList<Element>();
		while (iter.hasNext()) {
			EObject next = (EObject) iter.next();
			if (next instanceof Element) {
				Element element = (Element) next;
				if (this.filter.accept(element)) {
					list.add(element);
				}
			}
		}
		return list;
	}

	/**
	 * Return the selected value.
	 * 
	 * @return the selected value.
	 */
	public Element getSelectedValue() {
		return selectedValue;
	}

	@Override
	protected void cancelPressed() {
		// TODO Auto-generated method stub
		super.cancelPressed();
		selectedValue = null;
	}

	/**
	 * Sets the selection, i.e. the element that will be returned by the dialog.
	 * 
	 * @param e
	 *            the selection event
	 */
	protected void setSelection(SelectionEvent e) {
		Widget selectedWidget = e.item;
		if (this.filter.accept((Element) selectedWidget.getData())) {
			selectedValue = (Element) selectedWidget.getData();
			getButton(IDialogConstants.OK_ID).setEnabled(true);
		} else {
			// sets association to null, if selected element is not an type.
			// erase a previous selected type
			// example: select type T1, then a package
			// if association is not set to null, it keeps the type T1 as result
			// whereas the package is selected
			// TODO should improve the dialog: Ok should not be enabled when
			// type is null
			// TODO should improve the dialog: selection should be initialized
			// depending on
			// the original type( ex open a window on an already typed element)
			selectedValue = null;
			getButton(IDialogConstants.OK_ID).setEnabled(false);
		}
	}

	/**
	 * Filter elements.
	 * 
	 * @author ymortier <yann.mortier@obeo.fr>
	 */
	public static interface UmlFilter {
		public boolean accept(Element element);
	}

	/**
	 * 
	 * @param metaClass
	 * @return
	 */
	public UmlFilter createMetaClassFilter(final EClass metaClass) {
		return new UmlFilter() {
			public boolean accept(Element element) {
				return metaClass.isSuperTypeOf(element.eClass());
			}
		};
	}

	/**
	 * Adds a filter.
	 * 
	 * @param filter
	 *            the filter.
	 */
	public void addFilter(UmlFilter umlFilter) {
		this.filter.addFilter(umlFilter);
	}

	/**
	 * Removes a filter.
	 * 
	 * @param filter
	 *            the filter.
	 */
	public void removeFilter(UmlFilter filter) {
		this.filter.removeFilter(filter);
	}

	/**
	 * The chain filter.
	 * 
	 * @author ymortier <yann.mortier@obeo.fr>
	 */
	private static class ChainFilter implements
			UmlGenericTreeSelectionDialog.UmlFilter {

		/** all filters. */
		private List<UmlGenericTreeSelectionDialog.UmlFilter> filters;

		/**
		 * Creates a new <code>ChainFilter</code>.
		 */
		public ChainFilter() {
			this.filters = new ArrayList<UmlFilter>();
		}

		/**
		 * @see com.cea.papyrus.core.ui.dialogs.UmlGenericTreeSelectionDialog.UmlFilter#accept(org.eclipse.uml2.uml.Element)
		 */
		public boolean accept(Element element) {
			Iterator<UmlFilter> iterFilters = filters.iterator();
			boolean accept = true;
			while (iterFilters.hasNext() && accept) {
				accept = iterFilters.next().accept(element);
			}
			return accept;
		}

		/**
		 * Adds a filter.
		 * 
		 * @param filter
		 *            the filter.
		 */
		public void addFilter(UmlFilter filter) {
			this.filters.add(filter);
		}

		/**
		 * Removes a filter.
		 * 
		 * @param filter
		 *            the filter.
		 */
		public void removeFilter(UmlFilter filter) {
			this.filters.remove(filter);
		}

	}

	public static List<Element> getAllParentsUntil(Package package_,
			Element element) {
		LinkedList<Element> list = new LinkedList<Element>();
		while (!element.equals(package_)) {
			list.addFirst(element);
			element = element.getOwner();
		}
		return list;
	}

	private static TreeItem findOrCreateTreeItem(TreeItem parent,
			Element element) {
		TreeItem res = null;
		TreeItem[] children = parent.getItems();
		for (int i = 0; i < children.length && res == null; i++) {
			if (children[i].getData().equals(element)) {
				res = children[i];
			}
		}
		if (res == null) {
			res = new TreeItem(parent, SWT.NONE);
			res
					.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(element,
							true));
			res.setData(element);
			res.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(element));
		}
		return res;
	}
}
